﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Drawing;

namespace Vortex.SceneFramework.Gui {

	//-------------------------------------------------------------------------
	///<summary>Defines kind of flow layout alignment</summary>
	[Flags]
	public enum FlowLayout : ushort {
		///<summary>Default layout: Left, Top</summary>
		Default = 0,
		///<summary>Horizontal layout: Left</summary>
		Left = 0x0001,
		///<summary>Horizontal layout: Right</summary>
		Right = 0x0002,
		///<summary>Horizontal layout: Center</summary>
		Center = 0x0003,
		///<summary>Vertical layout: Top</summary>
		Top = 0x0100,
		///<summary>Vertical layout: Bottom</summary>
		Bottom = 0x0200,
		///<summary>Vertical layout: Middle</summary>
		Middle = 0x0300
	};

	/// <summary>
	/// Exposes flow layout algorithm with different spacings and alignments.
	/// All of widgets inside layout can be located according to rules. 
	/// Flow panel implements own children rearrangement flow algorithm. It doesn't use FillParent property. Use <see cref="Panel"/> if you need very simple container.
	/// </summary>
	public class FlowPanel : Panel {
		private Vector2 _spacing;
		private FlowLayout _layout;
		private int _maxWidgetsPerRow = 0;

		#region Properties

		/// <summary>
		/// Gets or sets the spacing between widgets in layout.
		/// </summary>
		/// <value>The spacing between widgets.</value>
		public Vector2 Spacing {
			get { return _spacing; }
			set { _spacing = value; RearrangeChildren(); }
		}

		/// <summary>
		/// Gets or sets the row spacing.
		/// </summary>
		/// <value>The widget row spacing.</value>
		public float RowSpacing {
			get { return _spacing.Y; }
			set { _spacing.Y = value; }
		}

		/// <summary>
		/// Gets or sets the widget spacing (space between widgets in the row).
		/// </summary>
		/// <value>The widget spacing.</value>
		public float WidgetSpacing {
			get { return _spacing.X; }
			set { _spacing.X = value; }
		}

		/// <summary>
		/// Gets or sets the layout of widgets in panel.
		/// </summary>
		/// <value>The layout of widgets in panel.</value>
		public FlowLayout Layout {
			get { return _layout; }
			set { _layout = value; }
		}

		/// <summary>
		/// Gets or sets the max widgets in layout widget row.
		/// </summary>
		/// <value>The max allowed widgets in layout widget row. <c>0</c> means unlimited widgets in row</value>
		public int MaxWidgetsPerRow {
			get { return _maxWidgetsPerRow; }
			set {
				if (value < 0) throw new ArgumentException("Number of widgets per row should be not less than zero", "MaxWidgetsInRow");
				_maxWidgetsPerRow = value;
			}
		}

		#endregion


		/// <summary>
		/// Rearranges children in flow manner, using configuration of FlowLayout.
		/// </summary>
		public override void RearrangeChildren() {
			//check quit nested rearrangement
			if (!CanRearrangeChildren()) return;

			//set recursion guard flag
			LockRearrangement();

			try {
				List<ViewNode> widgets = new List<ViewNode>(GetChildren<ViewNode>());

				if (widgets.Count > 0) {
					float totalRowsHeight = 0;
					PanelRow row;
					List<PanelRow> rows = new List<PanelRow>();

					while ((row = ExtractRow(widgets, Size.X, Spacing.X)) != null) {
						totalRowsHeight += row.Height;
						rows.Add(row);
					}
					//add spacing between rows
					totalRowsHeight += (rows.Count - 1) * Spacing.Y;

					//prepare bounding box for 
					Rect elementBounds = BoundingBox;
					Vector2 widgetCursor = elementBounds.LeftTop;
					if ((_layout & FlowLayout.Bottom) == FlowLayout.Bottom) {
						widgetCursor.Y +=
							(elementBounds.Height - totalRowsHeight) *
							(((_layout & FlowLayout.Top) == FlowLayout.Top) ? 0.5f : 1.0f);
					}

					//local all of rows
					foreach (PanelRow trow in rows) {
						widgetCursor.X = elementBounds.Left;
						if ((_layout & FlowLayout.Right) == FlowLayout.Right) {
							widgetCursor.X +=
								(elementBounds.Width - trow.Width) *
								(((_layout & FlowLayout.Center) == FlowLayout.Center) ? 0.5f : 1.0f);
						}

						foreach (ViewNode widget in trow.Widgets) {
							widget.Bounds = Rect.FromBox(widgetCursor + widget.Margin.LeftTop, widget.Size);
							widgetCursor.X += widget.OuterBounds.Width + trow.Spacing;
						}
						//increase widget cursor
						widgetCursor.Y += trow.Height + _spacing.Y;
					}
				}
			} finally {
				UnlockRearrangement();
			}
		}

		/// <summary>
		/// Extracts the row of widgets.
		/// </summary>
		/// <param name="widgets">The widget list.</param>
		/// <param name="maxWidth">Maximal width of row.</param>
		/// <param name="spacing">The spacing between widgets in row.</param>
		/// <returns>If there some items in row - row; otherwise <c>null</c></returns>
		private PanelRow ExtractRow(List<ViewNode> widgets, float maxWidth, float spacing) {
			PanelRow row = new PanelRow(spacing);
			foreach (ViewNode widget in widgets) {
				Rect widgetOuterBounds = widget.OuterBounds;
				if (
					(_maxWidgetsPerRow == 0 || row.WidgetCount < _maxWidgetsPerRow) &&
					(row.WidgetCount == 0 || maxWidth >= row.Width + widgetOuterBounds.Width)
				) {
					row.Add(widget);
				} else {
					break;
				}
			}
			//if there are some widgets in row we need to modify
			if (row.WidgetCount > 0) {
				widgets.RemoveRange(0, row.WidgetCount);
				return row;
			} else {
				return null;
			}
		}

		#region LayoutRow
		/// <summary>
		/// Temporary contains information about widget row in panel
		/// </summary>
		private class PanelRow {
			private float _spacing;
			private float _cleanWidth = 0;
			private float _height = 0;
			private List<ViewNode> _widgets = new List<ViewNode>();

			/// <summary>
			/// Initializes a new instance of the <see cref="LayoutRow"/> class.
			/// </summary>
			/// <param name="spacing">The spacing between widgets.</param>
			public PanelRow(float spacing) {
				_spacing = spacing;
			}

			/// <summary>
			/// Gets the spacing between widgets in row.
			/// </summary>
			/// <value>The spacing between widget in row.</value>
			public float Spacing {
				get { return _spacing; }
			}

			/// <summary>
			/// Gets the clean width of row (only widget sum width).
			/// </summary>
			/// <value>The clean width of row.</value>
			public float CleanWidth {
				get { return _cleanWidth; }
			}

			/// <summary>
			/// Gets the width of row including spacings.
			/// </summary>
			/// <value>The width of row.</value>
			public float Width {
				get {
					float totalSpacing = _widgets.Count == 0 ? 0.0f : (_widgets.Count - 1) * _spacing;
					return CleanWidth + totalSpacing; 
				}
			}

			/// <summary>
			/// Gets the height of row.
			/// </summary>
			/// <value>The height of row.</value>
			public float Height {
				get { return _height; }
			}

			/// <summary>
			/// Gets the widget count in row.
			/// </summary>
			/// <value>The widget count in row.</value>
			public int WidgetCount {
				get { return _widgets.Count; }
			}

			/// <summary>
			/// Adds the specified widget into row.
			/// </summary>
			/// <param name="widget">The widget.</param>
			public void Add(ViewNode widget) {
				_widgets.Add(widget);
				Rect outerBounds = widget.OuterBounds;
				_cleanWidth += outerBounds.Width;
				_height = Math.Max(_height, outerBounds.Height);
			}

			/// <summary>
			/// Gets the widgets of row.
			/// </summary>
			/// <value>The widgets of row.</value>
			public IEnumerable<ViewNode> Widgets {
				get { return _widgets; }
			}
		}

		#endregion

	}
}
